package com.plumecache.core;

import com.plumecache.core.annotation.CacheCompress;
import com.plumecache.core.compressor.CacheCompressor;
import com.plumecache.core.serializer.CacheSerializer;
import com.plumecache.core.serializer.ObjectCacheSerializer;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import sun.misc.BASE64Decoder;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;


@Slf4j
public abstract class BaseCacheService implements CacheService {

    protected CacheSerializer cacheSerializer;
    protected CacheCompressor cacheCompressor;
    protected InstanceProperties instanceProperties;

    @SneakyThrows
    public <T> String serialize(T value) {
        String serialize;
        if (value instanceof ObjectCacheSerializer) {
            ObjectCacheSerializer objectCacheSerializer = (ObjectCacheSerializer) value;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
            objectCacheSerializer.serialize(objectOutputStream);
            serialize = new sun.misc.BASE64Encoder().encode(baos.toByteArray());
        } else {
            serialize = cacheSerializer.serialize(value);
        }

        if (serialize != null && value.getClass().isAnnotationPresent(CacheCompress.class)) {
            return cacheCompressor.compress(serialize);
        }
        return serialize;
    }

    @SneakyThrows
    protected <T> T deserialize(String value, Class<T> clazz) {
        if (value != null && clazz.isAnnotationPresent(CacheCompress.class)) {
            value = cacheCompressor.decompress(value);
        }

        if (ObjectCacheSerializer.class.isAssignableFrom(clazz)) {
            byte[] bytes = new BASE64Decoder().decodeBuffer(value);
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            ObjectInputStream objectInputStream = new ObjectInputStream(bais);
            T instance = clazz.newInstance();
            ObjectCacheSerializer objectCacheSerializer = (ObjectCacheSerializer) instance;
            objectCacheSerializer.deserialize(objectInputStream);
            return instance;
        }

        return cacheSerializer.deserialize(value, clazz);
    }

    @SneakyThrows
    @Override
    public void initialize(InstanceProperties properties) {
        this.instanceProperties = properties;

        String serializer = instanceProperties.getSerializer();
        if (StringUtils.isBlank(serializer)) {
            serializer = "com.plumecache.core.serializer.GsonCacheSerializer";
        }

        //set serializer
        this.cacheSerializer = (CacheSerializer) Class.forName(serializer).newInstance();
        log.info("cache serializer init...{}", serializer);

        String compressor = instanceProperties.getCompressor();
        if (StringUtils.isBlank(compressor)) {
            compressor = "com.plumecache.core.compressor.GzipCacheCompressor";
        }

        //set compressor
        this.cacheCompressor = (CacheCompressor) Class.forName(compressor).newInstance();
        log.info("cache compressor init...{}", compressor);
    }

    @Override
    public InstanceProperties getProperties() {
        return instanceProperties;
    }

    @Override
    public String get(String key) {
        return get(key, String.class);
    }

    @Override
    public Long ttl(String key) {
        throw new NotImplementedException();
    }

    @Override
    public <T> T hget(String key, String field, Class<T> clazz) {
        throw new NotImplementedException();
    }

    @Override
    public <T> void hset(String key, String field, T value) {
        throw new NotImplementedException();
    }

    @Override
    public long hdel(String key, String field) {
        throw new NotImplementedException();
    }

    @Override
    public <T> Map<String, T> hgetAll(String key, Class<T> clazz) {
        throw new NotImplementedException();
    }

    @Override
    public Map<String, String> hgetAll(String key) {
        return hgetAll(key, String.class);
    }

    @Override
    public String hget(String key, String field) {
        return hget(key, field, String.class);
    }
}
